useReducer 是 useState 的一個替代方案,用於處理比較複雜的狀態,或是裡面有多個屬性的 state,類似於 redux 中的 reducer,不同的地方在於它多了兩個參數 reducer、initialAction。
const [state, setState] = useState(initialState)
const [state, dispatch] = useReducer(reducer, initialState, initialAction)
state 就是資料的狀態。dispatch 是更新 state 的方法,跟 setState 的差別在於它接受 action 作為參數。reducer 是一個函式,用於處理 action 的描述,並以此更新 state。它接受兩個參數, state(舊state) 和 action,並返回一個新的 state,如下面的結構(state, action) => newState
action 是一個物件,用於描述 state 更新的方式,它通常有一個 type 屬性,也可以攜帶其他參數,下面是一個 action 的範例,action 只能透過 dispatch 來調用,dispatch 會將 action 傳給 reducer。const action = {
type: 'increment', // increment 表示 state 的值要增加
payload: {
other: 'value' // 其他的參數
}
}
initialState 就是初始的 state。initialAction 是 useReducer 第一次被執行時的 action。下面是一個簡易的 reducer 使用範例,我們利用三個 button 來使用不同的 action type,這樣當 dispatch 調用 action 時,觸發 reducer,就能起到不同的效果。
import { useReducer } from 'react';
//初始值為 0
const initialState = { count: 0 }
// 接受 state, action 參數
const reducer = (state, action) => {
// 根據 action 的 type 來更新 state
switch (action.type) {
// type 為 reset,就重新轉為初始值
case 'reset':
return initialState
// type 為 increment,就 +1
case 'increment':
return {count: state.count + 1}
// type 為 decrement,就 -1
case 'decrement':
return {count: state.count - 1}
// 當 type 不屬於上面任何值,就返回當前的 state
default:
return state
}
}
const TestComponent = () => {
const [state, dispatch] = useReducer(reducer, initialState)
// 透過 dispatch 調用 action,來判斷更新的方式
return <div>
<p>current count {state.count}</p>
<div><button onClick={() => dispatch({type: 'reset'})}>reset</button></div>
<div><button onClick={() => dispatch({type: 'increment'})}>+1</button></div>
<div><button onClick={() => dispatch({type: 'decrement'})}>-1</button></div>
</div>
}
上一篇有提到我們可以利用 useContext 做到 state 的全域調用,我們也可以搭配 useContext 來使用 useReducer 以達到較為複雜的 state 管理及更新。下面我們先建立初始值和 Context
const userInitState = {
name: 'Leo',
age: 29
}
export const UserContext = React.createContext({
userInitState
});
接著我們建立更新 state 用的 reducer
const userReducer = (state, action) => {
switch (action.type) {
case 'reset':
return userInitState
case 'changeName':
return {name: 'Jack', age: userInitState.age}
case 'ChangeAge':
return {name: userInitState.name ,age: 25}
default:
return state
}
}
我們接著在 UserProfile 中去調用 useReducer,並將 userState 和 userDispatch 透過 provider 傳送到 TestComponent1 及 TestComponent2元件
const UserProfile = () => {
const [userState, userDispatch] = useReducer(userReducer, userInitState);
return <div>
<UserContext.Provider
value={{
userState,
userDispatch
}}
>
<TestComponent1 />
<TestComponent2 />
</UserContext.Provider>
</div>
}
這樣我們就能在 TestComponent1 及 TestComponent2元件 中調用 userDispatch 並同步更新 userState,達到管理全域狀態並更新的效果。
const TestComponent1 = (props) => {
const { userState, userDispatch } = React.useContext(UserContext);
// 調用 userDispatch
return <div>
<button onClick={() => userDispatch({type: 'changeName'})}>change Name</button>
<button onClick={() => userDispatch({type: 'ChangeAge'})}>change Age</button>
<button onClick={() => userDispatch({type: 'reset'})}>reset data</button>
</div>
}
const TestComponent2 = (props) => {
const { userState, userDispatch } = React.useContext(UserContext);
// 同步更新 userState
return <div>
<div>name: {userState.name}</div>
<div>age: {userState.age}</div>
</div>
}